// Forked from https://github.com/codeslinger/log.git
// AllRight hold by codeslinger

// vim:set ts=2 sw=2 et ai ft=go:
package log

import (
  "fmt"
  "io"
  "os"
  "runtime"
  "strings"
  "sync"
  "time"
)

// --- LOG LEVELS -----------------------------------------------------------

// Priority level of log message.
type Level int

const (
  TRACE Level = iota
  DEBUG
  INFO
  WARN
  ERROR
  FATAL
)

const (
  timeFormat = "2006/01/02 15:04:05"
)

var levelStrings = [...]string{"[T]", "[D]", "[I]", "[W]", "[E]", "[F]"}

// String representation of priority level.
func (l Level) String() string {
  if l < 0 || int(l) > len(levelStrings) {
    return "UNKNOWN"
  }
  return levelStrings[int(l)]
}

// --- LOGGER API -----------------------------------------------------------

// Logger allows for serialized output to a sink of log messages, filtering
// any incoming messages below the specified priority level. The Logger is
// safe to use from concurrent goroutines.
type Logger struct {
  mutex     sync.Mutex
  level     Level
  sink      io.Writer
  buf       []byte
  callDepth int
}

// Create a new Logger that outputs to the given sink and has the given
// minimum priority level.
func NewLogger(out io.Writer, lvl Level) *Logger {
  return newLogger(out, lvl, 2)
}

// Get the lowest priority level for which this Logger will emit messages.
func (log *Logger) GetLevel() Level {
  return log.level
}

// Set the lowest priority level for which this Logger will emit messages.
func (log *Logger) SetLevel(lvl Level) {
  if lvl < TRACE || lvl > FATAL {
    return
  }
  log.level = lvl
}

// Set the output destination of the logger
func (log *Logger) SetOutput(w io.Writer) {
  log.sink = w
}

// Set the lowest priority level by a level string(["TRACE","DEBUG","INFO","WARN","ERROR","FATAL"])
// for which this Logger will emit messages.
func (log *Logger) SetLevelFromString(level string) {
  switch level {
  case "TRACE":
    log.level = TRACE
  case "DEBUG":
    log.level = DEBUG
  case "INFO":
    log.level = INFO
  case "WARN":
    log.level = WARN
  case "ERROR":
    log.level = ERROR
  case "FATAL":
    log.level = FATAL
  default:
    log.level = TRACE
  }
}

// Log a TRACE level message to this Logger.
func (log *Logger) Trace(arg0 interface{}, args ...interface{}) {
  switch first := arg0.(type) {
  case string:
    log.logf(TRACE, first, args...)
  case func() string:
    log.logc(TRACE, first)
  default:
    log.logf(TRACE,
      fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
  }
}

// Log a DEBUG level message to this Logger.
func (log *Logger) Debug(arg0 interface{}, args ...interface{}) {
  switch first := arg0.(type) {
  case string:
    log.logf(DEBUG, first, args...)
  case func() string:
    log.logc(DEBUG, first)
  default:
    log.logf(DEBUG,
      fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
  }
}

// Log an INFO level message to this Logger.
func (log *Logger) Info(arg0 interface{}, args ...interface{}) {
  switch first := arg0.(type) {
  case string:
    log.logf(INFO, first, args...)
  case func() string:
    log.logc(INFO, first)
  default:
    log.logf(INFO,
      fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
  }
}

// Log a WARN level message to this Logger.
func (log *Logger) Warn(arg0 interface{}, args ...interface{}) {
  switch first := arg0.(type) {
  case string:
    log.logf(WARN, first, args...)
  case func() string:
    log.logc(WARN, first)
  default:
    log.logf(WARN,
      fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
  }
}

// Log an ERROR level message to this Logger.
func (log *Logger) Error(arg0 interface{}, args ...interface{}) {
  switch first := arg0.(type) {
  case string:
    log.logf(ERROR, first, args...)
  case func() string:
    log.logc(ERROR, first)
  default:
    log.logf(ERROR,
      fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
  }
}

// Log a CRITICAL level message to this Logger. This will also generate a panic
// with the log message after the message has been written to the log.
func (log *Logger) Fatal(arg0 interface{}, args ...interface{}) {
  msg := ""
  switch first := arg0.(type) {
  case string:
    msg = log.logf(FATAL, first, args...)
  case func() string:
    msg = log.logc(FATAL, first)
  default:
    msg = log.logf(
      FATAL,
      fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
  }
  panic(msg)
}

// --- SINGLETON INTERFACE ---------------------------------------------------

// The default logger will write to STDERR and emit messages for all priority
// levels.
var dfl = newLogger(os.Stderr, TRACE, 3)

// Get the lowest priority level for which the default logger will emit a
// message.
func GetLevel() Level {
  return dfl.GetLevel()
}

// Set the lowest priority level for which the default logger will emit a
// message.
func SetLevel(lvl Level) {
  dfl.SetLevel(lvl)
}

// Set the output destination for which the default logger will emit a
// message.
func SetOutput(w io.Writer) {
  dfl.SetOutput(w)
}

// Set the lowest priority level by a level string(["TRACE","DEBUG","INFO","WARN","ERROR","FATAL"])
// for which the default logger will emit a message.
func SetLevelFromString(lvl string) {
  dfl.SetLevelFromString(lvl)
}

// Log a TRACE level message to the default logger.
func Trace(arg0 interface{}, args ...interface{}) {
  dfl.Trace(arg0, args...)
}

// Log a TRACE level message to the default logger.
func Debug(arg0 interface{}, args ...interface{}) {
  dfl.Debug(arg0, args...)
}

// Log a TRACE level message to the default logger.
func Info(arg0 interface{}, args ...interface{}) {
  dfl.Info(arg0, args...)
}

// Log a TRACE level message to the default logger.
func Warn(arg0 interface{}, args ...interface{}) {
  dfl.Warn(arg0, args...)
}

// Log a TRACE level message to the default logger.
func Error(arg0 interface{}, args ...interface{}) {
  dfl.Error(arg0, args...)
}

// Log a CRITICAL level message to the default logger. This will also
// generate a panic with the log message after the message has been written
// to the log.
func Fatal(arg0 interface{}, args ...interface{}) {
  dfl.Fatal(arg0, args...)
}

// --- CLOSURE FACTORY ------------------------------------------------------

// Build a closure to generate a log message. This is used to defer
// potentially expensive computation in the case where the log message is
// not likely to be emitted given its low priority level.
func Closure(format string, args ...interface{}) func() string {
  return func() string {
    return fmt.Sprintf(format, args...)
  }
}

// --- INTERNAL FUNCTIONS ---------------------------------------------------

// Generate a new Logger instance. This constructor allows us to specify the
// callDepth, needed because the singleton instance uses a different depth
// than a regular Logger instance.
func newLogger(out io.Writer, lvl Level, callDepth int) *Logger {
  return &Logger{sink: out, level: lvl, callDepth: callDepth}
}

// Log a message via format string and args.
func (log *Logger) logf(lvl Level, format string, args ...interface{}) string {
  if lvl < log.level {
    return ""
  }

  _, file, line, ok := runtime.Caller(log.callDepth)
  if !ok {
    file = "???"
    line = 0
  }

  msg := format
  if len(args) > 0 {
    msg = fmt.Sprintf(format, args...)
  }
  log.write(lvl, time.Now(), file, line, msg)
  return msg
}

// Log a message via a call to a closure.
func (log *Logger) logc(lvl Level, closure func() string) string {
  if lvl < log.level {
    return ""
  }

  _, file, line, ok := runtime.Caller(log.callDepth)
  if !ok {
    file = "???"
    line = 0
  }

  msg := closure()
  log.write(lvl, time.Now(), file, line, msg)
  return msg
}

// Write a message to the log sink.
func (log *Logger) write(lvl Level, now time.Time, file string, line int, msg string) error {
  log.mutex.Lock()
  defer log.mutex.Unlock()
  log.buf = log.buf[:0]
  log.fmtPrefix(&log.buf, lvl, now, file, line)
  log.buf = append(log.buf, msg...)
  if len(msg) > 0 && msg[len(msg)-1] != '\n' {
    log.buf = append(log.buf, '\n')
  }
  _, err := log.sink.Write(log.buf)
  return err
}

// Concatenate the log message prefix to the given byte array.
func (log *Logger) fmtPrefix(buf *[]byte, lvl Level, t time.Time, file string, line int) {
  hdr := fmt.Sprintf("%s %s %s:%d ",
    levelStrings[int(lvl)],
    t.Format(timeFormat),
    log.fileBasename(file),
    line,
  )
  *buf = append(*buf, hdr...)
}

// Determine a the base name of a file. (i.e. shortname)
func (log *Logger) fileBasename(file string) string {
  return file[strings.LastIndex(file, "/")+1:]
}
